{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 支持向量机\n",
    "&emsp;&emsp;上篇主要介绍了神经网络。首先从生物学神经元出发，引出了它的数学抽象模型--MP神经元以及由两层神经元组成的感知机模型，并基于梯度下降的方法描述了感知机模型的权值调整规则。由于简单的感知机不能处理线性不可分的情形，因此接着引入了含隐层的前馈型神经网络，BP神经网络则是其中最为成功的一种学习方法，它使用误差逆传播的方法来逐层调节连接权。最后简单介绍了局部/全局最小以及目前十分火热的深度学习的概念。本篇围绕的核心则是曾经一度取代过神经网络的另一种监督学习算法——**支持向量机**（Support Vector Machine），简称**SVM**。  \n",
    "&emsp;&emsp;支持向量机是一种经典的二分类模型，基本模型定义为特征空间中最大间隔的线性分类器，其学习的优化目标便是间隔最大化，因此支持向量机本身可以转化为一个凸二次规划求解的问题。\n",
    "\n",
    "### 函数间隔与几何间隔\n",
    "&emsp;&emsp;对于二分类学习，假设现在的数据是线性可分的，这时分类学习最基本的想法就是找到一个合适的超平面，该超平面能够将不同类别的样本分开，类似二维平面使用$ax+by+c=0$来表示，超平面实际上表示的就是高维的平面，如下图所示：  \n",
    "<br/><center>\n",
    "<img style=\"border-radius: 0.3125em;box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);\" src=\"../images/6-1-Hyperplane.png\"><br><div style=\"color:orange; border-bottom: 1px solid #d9d9d9;display: inline-block;color: #000;padding: 2px;\">图6-1 超平面</div></center>\n",
    "\n",
    "&emsp;&emsp;对数据点进行划分时，易知：当超平面距离与它最近的数据点的间隔越大，分类的鲁棒性越好，即当新的数据点加入时，超平面对这些点的适应性最强，出错的可能性最小。因此需要让所选择的超平面能够最大化这个间隔Gap（如下图所示）， 常用的间隔定义有两种，一种称之为函数间隔，一种为几何间隔，下面将分别介绍这两种间隔，并对SVM为什么会选用几何间隔做了一些阐述。\n",
    "<br/><center>\n",
    "<img style=\"border-radius: 0.3125em;box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);\" src=\"../images/6-2-Gap.png\"><br><div style=\"color:orange; border-bottom: 1px solid #d9d9d9;display: inline-block;color: #000;padding: 2px;\">图6-2 间隔</div></center>\n",
    "\n",
    "#### 函数间隔\n",
    "&emsp;&emsp;在超平面$w^T x+b=0$确定的情况下，$|w^T x^*+b|$能够代表点$x^*$距离超平面的远近，易知：当$w^Tx^*+b>0$时，表示$x^*$在超平面的一侧（正类，类标为1），而当$w^Tx^*+b<0$时，则表示$x^*$在超平面的另外一侧（负类，类别为-1），因此$(w^Tx^*+b)y^*$ 的正负性恰能表示数据点$x^*$是否被分类正确。于是便引出了**函数间隔** 的定义（functional margin）:$$\\hat{\\gamma} = y(w^T x + b) = yf(x)$$&emsp;&emsp;而超平面$(w,b)$关于所有样本点$(x_i,y_i)$的函数间隔最小值则为超平面在训练数据集T上的函数间隔：$$\\hat{\\gamma}=\\min \\hat{\\gamma}_i,\\quad i=1,2,\\dots,n$$&emsp;&emsp;可以看出：这样定义的函数间隔在处理SVM上会有问题，当超平面的两个参数$w$和$b$同比例改变时，函数间隔也会跟着改变，但是实际上超平面还是原来的超平面，并没有变化。例如：$w_1 x_1+w_2 x_2+w_3 x_3+b=0$其实等价于$2w_1x_1+2w_2x_2+2w_3x_3+2b=0$，但计算的函数间隔却翻了一倍。从而引出了能真正度量点到超平面距离的概念——几何间隔（geometrical margin）。\n",
    "\n",
    "#### 几何间隔\n",
    "&emsp;&emsp;**几何间隔**代表的则是数据点到超平面的真实距离，对于超平面$w^T x+b=0$，$w$代表的是该超平面的法向量，设$x^*$为超平面外一点$x$在法向量$w$方向上的投影点，$x$与超平面的距离为$\\gamma$，则有$x^*=x-\\gamma(\\|w\\| w)$，又$x^*$在超平面上，即$w^T x^*+b=0$，代入即可得：$$\\gamma=\\frac{w^Tx+b}{\\|w\\|}=\\frac{f(x)}{\\|w\\|}$$为了得到$\\gamma$的绝对值，令$\\gamma$乘以其对应的类别$y$，即可得到几何间隔的定义：$$\\tilde{\\gamma}=y\\gamma = \\frac{\\hat{\\gamma}}{\\|w\\|}$$从上述函数间隔与几何间隔的定义可以看出：实质上函数间隔就是$|w^T x+b|$，而几何间隔就是点到超平面的距离。\n",
    "\n",
    "### 最大间隔与支持向量\n",
    "&emsp;&emsp;通过前面的分析可知：函数间隔不适合用来最大化间隔，因此这里我们要找的最大间隔指的是几何间隔，于是最大间隔分类器的目标函数定义为：$$\\begin{array}{ll}\n",
    "{\\max} & {\\tilde{\\gamma}} \\\\ \n",
    "\\text { s.t. } & y_i (w^T x_i+b ) = \\hat{\\gamma}_i \\geqslant \\hat{\\gamma}, \\quad i=1,2, \\ldots, n\\end{array}$$一般地，我们令$\\hat{\\gamma}$为1（这样做的目的是为了方便推导和目标函数的优化），从而上述目标函数转化为：$$\\begin{array}{ll} \\max & \\displaystyle \\frac{1}{\\|w\\|} \\\\ \n",
    "\\text { s.t. } & y_{i}(w^T x_i+b ) \\geqslant 1, \\quad i=1,2, \\ldots, n \\end{array}$$&emsp;&emsp;对于$y(w^T x+b)=1$的数据点，即下图中位于$w^T x+b=1$或$w'x+b=-1$上的数据点，我们称之为**支持向量**（support vector），易知：对于所有的支持向量，它们恰好满足$y^*(w^Tx^*+b)=1$，而所有不是支持向量的点，有$y^*(w^Tx^*+b)>1$。\n",
    "\n",
    "<br/><center>\n",
    "<img style=\"border-radius: 0.3125em;box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);\" src=\"../images/6-3-Support-Vector-and-Margin.png\"><br><div style=\"color:orange; border-bottom: 1px solid #d9d9d9;display: inline-block;color: #000;padding: 2px;\">图6-3 支持向量与间隔</div></center>\n",
    "\n",
    "### 从原始优化问题到对偶问题\n",
    "&emsp;&emsp;对于上述得到的目标函数，求$\\displaystyle \\frac{1}{\\|w\\|}$的最大值相当于求$\\|w\\|^2$的最小值，因此很容易将原来的目标函数转化为：$$\\begin{array}{l} \\max & \\displaystyle \\frac{1}{2}\\|w\\|^2 \\\\ \n",
    "\\text { s.t. } & y_{i}(w^T x_i+b ) \\geqslant 1, \\quad i=1,2, \\ldots, n \\end{array}$$&emsp;&emsp;即变为了一个带约束的凸二次规划问题，按书上所说可以使用现成的优化计算包（QP优化包）求解，但由于SVM的特殊性，一般我们将原问题变换为它的**对偶问题**，接着再对其对偶问题进行求解。为什么通过对偶问题进行求解，有下面两个原因：\n",
    "\n",
    "- 一是因为使用对偶问题更容易求解；\n",
    "- 二是因为通过对偶问题求解出现了向量内积的形式，从而能更加自然地引出核函数。\n",
    "\n",
    "&emsp;&emsp;对偶问题，顾名思义，可以理解成优化等价的问题，更一般地，是将一个原始目标函数的最小化转化为它的对偶函数最大化的问题。对于当前的优化问题，首先我们写出它的朗格朗日函数：$$\n",
    "L(w, b, \\alpha)=\\frac{1}{2}\\|w\\|^2-\\sum_{i=1}^n \\alpha_i\\left(y_i (w^T x_i+b )-1 \\right)$$&emsp;&emsp;上式很容易验证：当其中有一个约束条件不满足时，$L$的最大值为$\\infty$（只需令其对应的$\\alpha$为 $\\infty$即可）；当所有约束条件都满足时，$L$的最大值为$\\displaystyle \\frac{1}{2}\\|w\\|^2$（此时令所有的$\\alpha$为0），因此实际上原问题等价于：$$\\min_{w, b} \\theta(w)=\\min_{w, b} \\max_{\\alpha_i \\geqslant 0} L(w, b, \\alpha)=p^*$$&emsp;&emsp;由于这个的求解问题不好做，因此一般我们将最小和最大的位置交换一下（需满足KKT条件） ，变成原问题的对偶问题：$$\n",
    "\\max_{\\alpha_i \\geqslant 0} \\min_{w, b} L(w, b, \\alpha)=d^*$$&emsp;&emsp;这样就将原问题的求最小变成了对偶问题求最大（用对偶这个词还是很形象），接下来便可以先求$L$对$w$和$b$的极小，再求$L$对$\\alpha$的极大。  \n",
    "（1）首先求$L$对$w$和$b$的极小，分别求$L$关于$w$和$b$的偏导，可以得出：$$\\begin{aligned} \\frac{\\partial L}{\\partial w} &=0 \\Rightarrow w=\\sum_{i=1}^n \\alpha_i y_i x_i \\\\ \n",
    "\\frac{\\partial L}{\\partial b} &=0 \\Rightarrow \\sum_{i=1}^n \\alpha_i y_i=0 \\end{aligned}$$将上述结果代入$L$得到：$$\\begin{aligned} L(w,b,\\alpha) \n",
    "&= \\frac{1}{2} \\sum_{i,j=1}^n \\alpha_i \\alpha_j y_i y_j x_i^T x_j - \\sum_{i,j=1}^n \\alpha_i \\alpha_j y_i y_j x_i^T x_j - b \\sum_{i=1}^n \\alpha_i y_i + \\sum_{i=1}^n \\alpha_i \\\\\n",
    "&= \\sum_{i=1}^n \\alpha_i - \\frac{1}{2} \\sum_{i,j=1}^n \\alpha_i \\alpha_j y_i y_j x_i^T x_j\n",
    "\\end{aligned}$$上式只包含$\\alpha$  \n",
    "（2）接着$L$关于$\\alpha$极大求解$\\alpha$（通过SMO算法求解，此处不做深入）。\n",
    "$$\\begin{array}{ll} \\displaystyle \\max_{\\alpha} & \\displaystyle \\sum_{i=1}^n \\alpha_i-\\frac{1}{2} \\sum_{i, j=1}^n \\alpha_i \\alpha_j y_i y_j x_i^T x_j \\\\ \n",
    "{\\text { s.t. }} & {\\alpha_i \\geqslant 0, i=1,\\ldots, n} \\\\ \n",
    "{} & {\\displaystyle \\sum_{i=1}^n \\alpha_i y_i=0}\n",
    "\\end{array}$$\n",
    "（3）最后便可以根据求解出的$\\alpha$，计算出$w$和$b$，从而得到分类超平面函数。\n",
    "$$\\begin{aligned} w^* &=\\sum_{i=1}^n \\alpha_i y_i x_i \\\\ \n",
    "b^* &=-\\frac{\\displaystyle \\max _{i : y_i=-1} w^{* T} x_i+\\min _{i : y_i=1} w^{* T} x_i}{2} \n",
    "\\end{aligned}$$\n",
    "&emsp;&emsp;在对新的点进行预测时，实际上就是将数据点$x^*$代入分类函数$f(x)=w^T x+b$中，若$f(x)>0$，则为正类，$f(x)<0$，则为负类，根据前面推导得出的$w$与$b$，分类函数如下所示，此时便出现了上面所提到的内积形式。\n",
    "$$\\begin{aligned} f(x)\n",
    "&= \\left(\\sum_{i=1}^n \\alpha_i y_i x_i \\right)^T x + b \\\\\n",
    "&= \\sum_{i=1}^n \\alpha_i y_i \\langle x_i,x \\rangle + b\n",
    "\\end{aligned}$$\n",
    "&emsp;&emsp;这里实际上只需计算新样本与支持向量的内积，因为对于非支持向量的数据点，其对应的拉格朗日乘子一定为0，根据最优化理论（K-T条件），对于不等式约束$y(w^T x+b)-1 \\geqslant 0$，满足：$$\\partial_i(y_i(w^Tx_i+b)-1)=0(即总有一个为0)$$\n",
    "\n",
    "### 核函数\n",
    "&emsp;&emsp;由于上述的超平面只能解决线性可分的问题，对于线性不可分的问题，例如：异或问题，我们需要使用核函数将其进行推广。一般地，解决线性不可分问题时，常常采用**映射**的方式，将低维原始空间映射到高维特征空间，使得数据集在高维空间中变得线性可分，从而再使用线性学习器分类。如果原始空间为有限维，即属性数有限，那么总是存在一个高维特征空间使得样本线性可分。若$\\emptyset$代表一个映射，则在特征空间中的划分函数变为：$$f(x)=w^T \\phi(x) + b$$&emsp;&emsp;按照同样的方法，先写出新目标函数的拉格朗日函数，接着写出其对偶问题，求$L$关于$w$和$b$的极大，最后运用SOM求解$\\alpha$。可以得出：  \n",
    "（1）原对偶问题变为：$$\\begin{array}{ll}\n",
    "{\\displaystyle \\max_{\\alpha}} & {\\displaystyle \\sum_{i=1}^n \\alpha_i-\\frac{1}{2} \\sum_{i, j=1}^n \\alpha_i \\alpha_j y_i y_j \\left\\langle\\phi\\left(x_i\\right), \\phi\\left(x_j \\right)\\right\\rangle} \\\\ \n",
    "{\\text { s.t. }} & {\\alpha_i \\geqslant 0, i=1, \\ldots, n} \\\\ \n",
    "{} & {\\displaystyle \\sum_{i=1}^n \\alpha_i y_i=0}\n",
    "\\end{array}$$（2）原分类函数变为：$$\n",
    "f(x)=\\sum_{i=1}^n \\alpha_i y_i\\left\\langle\\phi\\left(x_i\\right), \\phi(x)\\right\\rangle+ b$$&emsp;&emsp;求解的过程中，只涉及到了高维特征空间中的内积运算，由于特征空间的维数可能会非常大，例如：若原始空间为二维，映射后的特征空间为5维，若原始空间为三维，映射后的特征空间将是19维，之后甚至可能出现无穷维，根本无法进行内积运算了，此时便引出了**核函数**（Kernel）的概念。\n",
    "> **定义3（核：Kernel）** 核是一个函数$K$，对所有$x,z \\in \\mathcal{X}$，满足$K(x,z)=\\langle \\phi(x), \\phi(z) \\rangle$，这里$\\phi$是从$\\mathcal{X}$到内积特征空间$\\mathcal{F}$的映射  \n",
    "\n",
    "&emsp;&emsp;因此，核函数可以直接计算隐式映射到高维特征空间后的向量内积，而不需要显式地写出映射后的结果，它虽然完成了将特征从低维到高维的转换，但最终却是在低维空间中完成向量内积计算，与高维特征空间中的计算等效**（低维计算，高维表现）**，从而避免了直接在高维空间无法计算的问题。引入核函数后，原来的对偶问题与分类函数则变为：  \n",
    "（1）对偶问题：\n",
    "$$\\begin{aligned} \\max_{\\alpha} & \\sum_{i=1}^n \\alpha_i-\\frac{1}{2} \\sum_{i, j=1}^n \\alpha_i \\alpha_j y_i y_j K(x_i, x_j) \\\\\n",
    "\\text { s.t. } & \\alpha_i \\geqslant 0, i=1, \\ldots, n \\\\ \n",
    "& \\sum_{i=1}^n \\alpha_i y_i=0 \\end{aligned}$$\n",
    "（2）分类函数：\n",
    "$$f(x)=\\sum_{i=1}^n \\alpha_i y_i K(x_i, x)+b$$\n",
    "&emsp;&emsp;因此，在线性不可分问题中，核函数的选择成了支持向量机的最大变数，若选择了不合适的核函数，则意味着将样本映射到了一个不合适的特征空间，则极可能导致性能不佳。同时，核函数需要满足以下这个必要条件：\n",
    "> **定理6.1（核函数）** 令$\\mathcal{X}$为输入空间，$\\kappa(\\cdot, \\cdot)$是定义在$\\mathcal{X} \\times \\mathcal{X}$上的对称函数，则$\\kappa$是核函数当且仅当对于任意数据$D=\\{x_1,x_2,\\ldots,x_m\\}$，“核矩阵”（kernel matrix）$\\mathbf{K}$总是半正定的：\n",
    "$$\\mathbf{K}=\\left[\\begin{array}{ccccc}{\\kappa\\left(x_1, x_1\\right)} & {\\cdots} & {\\kappa\\left(x_1, x_j\\right)} & {\\cdots} & {\\kappa\\left(x_1, x_m\\right)} \\\\ {\\vdots} & {\\ddots} & {\\vdots} & {\\ddots} & {\\vdots} \\\\ {\\kappa\\left(x_i, x_1\\right)} & {\\cdots} & {\\kappa\\left(x_i, x_j\\right)} & {\\cdots} & {\\kappa\\left(x_i, x_m\\right)} \\\\ {\\vdots} & {\\ddots} & {\\vdots} & {\\ddots} & {\\vdots} \\\\ {\\kappa\\left(x_m, x_1\\right)} & {\\cdots} & {\\kappa\\left(x_m, x_j\\right)} & {\\cdots} & {\\kappa\\left(x_m, x_m\\right)}\\end{array}\\right]$$\n",
    "\n",
    "&emsp;&emsp;由于核函数的构造十分困难，通常我们都是从一些常用的核函数中选择，下面列出了几种常用的核函数：\n",
    "<br/><center>\n",
    "<img style=\"border-radius: 0.3125em;box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);\" src=\"../images/6-4-Common-Kernel-Functions.png\"><br><div style=\"color:orange; border-bottom: 1px solid #d9d9d9;display: inline-block;color: #000;padding: 2px;\">图6-4 常用核函数</div></center>\n",
    "\n",
    "### 软间隔支持向量机\n",
    "&emsp;&emsp;前面的讨论中，我们主要解决了两个问题：当数据线性可分时，直接使用最大间隔的超平面划分；当数据线性不可分时，则通过核函数将数据映射到高维特征空间，使之线性可分。然而在现实问题中，对于某些情形还是很难处理，例如数据中有**噪声**的情形，噪声数据（**outlier**）本身就偏离了正常位置，但是在前面的SVM模型中，我们要求所有的样本数据都必须满足约束，如果不要这些噪声数据还好，当加入这些outlier后导致划分超平面被挤歪了，如下图所示，对支持向量机的泛化性能造成很大的影响。\n",
    "<br/><center>\n",
    "<img style=\"border-radius: 0.3125em;box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);\" src=\"../images/6-5-Noise-Data.png\"><br><div style=\"color:orange; border-bottom: 1px solid #d9d9d9;display: inline-block;color: #000;padding: 2px;\">图6-5 噪声数据</div></center>\n",
    "\n",
    "&emsp;&emsp;为了解决这一问题，我们需要允许某一些数据点不满足约束，即可以在一定程度上偏移超平面，同时使得不满足约束的数据点尽可能少，这便引出了**“软间隔”支持向量机** 的概念\n",
    "\n",
    "- 允许某些数据点不满足约束$y(w^T x+b) \\geqslant 1$；\n",
    "- 同时又使得不满足约束的样本尽可能少。\n",
    "\n",
    "这样优化目标变为：\n",
    "$$\\begin{array}{l}\n",
    "\\displaystyle \\min_{w, b} & \\displaystyle \\frac{1}{2}\\|w\\|^2+C \\sum_{i=1}^m \\ell_{0/1} \\left( y_i(w^T x_i+b) -1 \\right) \\\\ \n",
    "& {\\ell_{0/1}(z)=\\left\\{\\begin{array}{ll}\n",
    "{1,} & {\\text { if } z<0 ;} \\\\ \n",
    "{0,} & {\\text { otherwise }} \\end{array}\\right.}\n",
    "\\end{array}$$&emsp;&emsp;如同阶跃函数，0/1损失函数虽然表示效果最好，但是数学性质不佳。因此常用其它函数作为“替代损失函数”。\n",
    "> hinge损失：$\\ell_{hinge}(z)=\\max(0,1-z)$；  \n",
    "指数损失（exponential loss）: $\\ell_{exp}(z)=\\exp (-z)$；  \n",
    "对率损失（logistic loss）：$\\ell_{log}(z)=log(1+\\exp(-z))$。\n",
    "\n",
    "&emsp;&emsp;支持向量机中的损失函数为**hinge损失**，引入**“松弛变量”**，目标函数与约束条件可以写为：\n",
    "$$\\begin{aligned} \\min & \\frac{1}{2}\\|w\\|^2 + C \\sum_{i=1}^n \\xi_i \\\\ \n",
    "& y_i (w^T x_i+b) \\geqslant 1-\\xi_i, \\quad i=1, \\ldots, n \\\\\n",
    "& \\xi_i \\geqslant 0, \\quad i=1, \\ldots, n \\end{aligned}$$\n",
    "\n",
    "&emsp;&emsp;其中$C$为一个参数，控制着目标函数与新引入正则项之间的权重，这样显然每个样本数据都有一个对应的松弛变量，用以表示该样本不满足约束的程度，将新的目标函数转化为拉格朗日函数得到：$$\n",
    "L(w, b, \\xi, \\alpha, r)=\\frac{1}{2}\\|w\\|^2+C \\sum_{i=1}^n \\xi_i-\\sum_{i=1}^n \\alpha_i\\left(y_i (w^T x_i+b)-1+\\xi_i\\right)-\\sum_{i=1}^n r_i \\xi_i\n",
    "$$&emsp;&emsp;按照与之前相同的方法，先让$L$求关于$w$，$b$以及松弛变量的极小，再使用SMO求出$\\alpha$，有：$$\\begin{aligned} \n",
    "\\frac{\\partial L}{\\partial w} &=0 \\Rightarrow w=\\sum_{i=1}^n \\alpha_i y_i x_i \\\\ \n",
    "\\frac{\\partial L}{\\partial b} &=0 \\Rightarrow \\sum_{i=1}^n \\alpha_i y_i=0 \\\\ \n",
    "\\frac{\\partial L}{\\partial \\xi_i} &=0 \\Rightarrow C-\\alpha_i-r_i=0, \\quad i=1, \\ldots, n \\end{aligned}$$将$w$代入$L$化简，便得到其对偶问题：\n",
    "$$\\begin{array}{cl}\n",
    "{\\displaystyle \\max_{\\alpha}} & {\\displaystyle \\sum_{i=1}^n \\alpha_i-\\frac{1}{2} \\sum_{i, j=1}^n \\alpha_i \\alpha_j y_i y_j x_i^T x_j} \\\\ \n",
    "{\\text {s.t.}} & {0 \\leqslant \\alpha_i \\leqslant C, i=1, \\ldots, n} \\\\\n",
    "{} & {\\displaystyle \\sum_{i=1}^n \\alpha_i y_i=0} \\end{array}$$\n",
    "&emsp;&emsp;将“软间隔”下产生的对偶问题与原对偶问题对比可以发现：新的对偶问题只是约束条件中的$\\alpha$多出了一个上限$C$，其它的完全相同，因此在引入核函数处理线性不可分问题时，便能使用与“硬间隔”支持向量机完全相同的方法。  \n",
    "----在此SVM就介绍完毕。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
